package fr.sunnydale.android.geek.thread;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import fr.sunnydale.android.geek.util.Speed;

/**
 * This is the actual clock of the system.
 * @author doctor
 */
public class TimerThread extends Thread {

    /**
     * Running flag.
     */
    public boolean mRun;
    /***
     * An array holding the last precise times a certain type of frame has been displayed. This is
     * actually initialized given the {@link Speed} number of elements. This array is useful for
     * background timing for example, since each type of Speed can be consumed only once by method
     * {@link TimerThread#hasToRefresh(Speed)}
     */
    public long[] mLastTime = new long[Speed.values().length];
    /**
     * This {@link Map} holds a {@link Collection} of {@link Map}, themselves containing lastRefresh
     * by id. Usually the id should be unique (use {@link Object#hashCode()} for instance).
     */
    public Map<Speed, Map<Integer, Long>> mAttachedObjects;

    /**
     * Thread constructor and initializer.
     * @param holder
     */
    public TimerThread() {
        mAttachedObjects = new HashMap<Speed, Map<Integer, Long>>();
        long startTime = System.currentTimeMillis();
        synchronized (this) {
            for (Speed speed : Speed.values()) {
                mLastTime[speed.index] = startTime;
                mAttachedObjects.put(speed, new HashMap<Integer, Long>());
            }
        }
    }

    /**
     * Tells you if given a {@link Speed} the frame should be refreshed.
     * @param speed
     *            A {@link Speed} element.
     * @return true if you have passed the time threshold given by the {@link Speed#speed}.
     */
    public boolean hasToRefresh(Speed speed) {
        boolean refresh = false;
        long thisTime = System.currentTimeMillis();
        refresh = hasToRefresh(speed, mLastTime[speed.index], thisTime);
        if (refresh) {
            mLastTime[speed.index] = thisTime;
        }
        return refresh;
    }

    /**
     * Tells you if the Object held by {@link TimerThread#mAttachedObjects} has to refresh.
     * @param id
     *            The id of the queried object.
     * @return true if you passed the time threshold.
     */
    public boolean hasToRefresh(int id) {
        boolean refresh = false;
        long thisTime = System.currentTimeMillis();
        Long lastTime = null;
        Map<Integer, Long> searchedMap = null;
        for (Speed speed : mAttachedObjects.keySet()) {
            searchedMap = mAttachedObjects.get(speed);
            if (searchedMap.containsKey(id)) {
                lastTime = searchedMap.get(id);
                if (lastTime != null) {
                    refresh = hasToRefresh(speed, lastTime, thisTime);
                    if (refresh) {
                        searchedMap.put(id, thisTime);
                    }
                }
                break;
            }
        }
        return refresh;
    }

    /**
     * Attach a new Object to {@link TimerThread#mAttachedObjects}.
     * @param speed
     *            The {@link Speed} it should be attached to.
     * @param id
     *            The unique id of the object.
     */
    public synchronized void attach(Speed speed, int id) {
        mAttachedObjects.get(speed).remove(id);
        mAttachedObjects.get(speed).put(id, System.currentTimeMillis());
    }

    public void detach(int id) {
        Map<Integer, Long> searchedMap = null;
        for (Speed speed : mAttachedObjects.keySet()) {
            searchedMap = mAttachedObjects.get(speed);
            if (searchedMap.containsKey(id)) {
                searchedMap.remove(id);
                break;
            }
        }
    }

    /**
     * Convenience method. Calculates if given the parameters the object should be refreshed.
     * @param speed
     *            The {@link Speed}.
     * @param lastTime
     *            Last time in milliseconds.
     * @param thisTime
     *            This time in milliseconds.
     * @return true if the time threshold has been passed.
     */
    private boolean hasToRefresh(Speed speed, long lastTime, long thisTime) {
        boolean refresh = false;
        if ((thisTime - lastTime) > speed.speed) {
            refresh = true;
        }
        return refresh;
    }
}
